Перейти к основному содержимому

7.06. Dockerfile

Разработчику Архитектору Инженеру

Dockerfile

Инструкции

Dockerfile — это текстовый файл, содержащий инструкции для автоматической сборки Docker-образа. Он является ключевым элементом по работе с Docker, так как позволяет создавать образы, которые можно использовать для запуска контейнеров. Dockerfile позволяет описать процесс создания образа шаг за шагом, что делает его воспроизводимым и легко поддерживаемым. Один и тот же Dockerfile может быть использован для создания одинаковых образов на разных машинах. Каждая инструкция в Dockerfile создаёт новый слой (layer) образа. Это позволяет эффективно использовать кэширование и уменьшать размер конечного образа.

Dockerfile — это ключевая документация образа, которая описывает все зависимости, переменные среды и команды. Когда мы запускаем команду docker build, Docker читает Dockerfile и выполняет инструкции по порядку. Каждая инструкция создаёт новый слой (layer) в образе. Слои кэшируются, что ускоряет процесс сборки при повторных запусках.

Разберём инструкции Dockerfile.

  1. FROM
FROM <image>:<tag>

Команда задаёт базовый образ, на основе которого будет создан новый образ. Это обязательная первая инструкция в Dockerfile. Пример:

FROM ubuntu:20.04

FROM может использоваться с алиасом для многоэтапной сборки:

FROM golang:1.20 AS builder
  1. LABEL
LABEL <key>=<value>

Добавляет метаданные к образу (например, автор, версия, описание). Метки могут быть просмотрены с помощью команды docker inspect. Пример:

LABEL maintainer="tim@mail.ru"
  1. ENV
ENV <key>=<value>

Устанавливает переменные среды, которые будут доступны внутри контейнера. Переменные сохраняются в образе и могут использоваться другими инструкциями. Пример:

ENV APP_HOME=/app
  1. RUN
RUN <command>

Выполняет команду в новом слое и создаёт новый образ. Используется для установки пакетов, настройки системы. Есть два формата:

  • Shell-формат: RUN <command> (выполняется через /bin/sh -c).
  • Exec-формат: RUN ["executable", "param1", "param2"].

Пример:

RUN apt-get update && apt-get install -y python3
  1. COPY
COPY <src> <dest>

Копирует файлы или папки из локальной файловой системы в контейнер.

  • <src> - путь к файлам/папкам на хосте.
  • <dest> - путь внутри контейнера.

Пример:

COPY . /app
  1. ADD
ADD <src> <dest>

Аналогично COPY, но также может распаковывать локальные .tar файлы. Не рекомендуется использовать, если не требуется распаковка архивов. Пример:

ADD archive.tar.gz /app
  1. CMD
CMD ["executable", "param1", "param2"]

Определяет команду, которая будет выполнена при запуске контейнера. Может быть переопределена при запуске контейнера через docker run. В файле может быть только одна инструкция CMD. Пример

CMD ["python3", "app.py"]
  1. ENTRYPOINT
ENTRYPOINT ["executable", "param1", "param2"]

Определяет команду, которая всегда будет выполняться при запуске контейнера. Аргументы, переданные через docker run, добавляются к команде ENTRYPOINT. Пример:

ENTRYPOINT ["python3", "app.py"]

По сути, это альтернатива CMD, но более гибкая. 9. WORKDIR

WORKDIR /path/to/workdir

Задаёт рабочую директорию для последующих инструкций (RUN, CMD, ENTRYPOINT). Если директория не существует, она будет создана. Пример:

WORKDIR /app
  1. ARG
ARG <name>[=<default value>]

Определяет переменную, которую можно передать во время сборки образа через флаг --build-arg. Пример:

ARG VERSION=1.0
  1. EXPOSE
EXPOSE <port>[/protocol]

Информирует Docker о том, что контейнер будет слушать указанный порт. Не открывает порт автоматически, используется для документации. Пример:

EXPOSE 8080
  1. VOLUME
VOLUME ["/data"]

Создаёт точку монтирования для работы с постоянным хранилищем. Позволяет сохранить данные вне контейнера. Пример:

VOLUME /var/lib/mysql
  1. USER
USER <user>[:<group>]

Задаёт пользователя и группу, от имени которых будут выполняться команды. По умолчанию команды выполняются от имени root. Пример:

USER appuser
  1. ONBUILD
ONBUILD <instruction>

Добавляет триггер для будущих сборок, основанных на текущем образе. Инструкция будет выполнена только при сборке дочернего образа. Пример:

ONBUILD COPY . /app
  1. STOPSIGNAL
STOPSIGNAL signal

Задаёт сигнал, который будет отправлен контейнеру при его остановке. Пример:

STOPSIGNAL SIGTERM
  1. SHELL
SHELL ["executable", "parameters"]

Переопределяет команду оболочку для выполнения инструкций. По умолчанию используется /bin/sh -c на Linux и cmd /S /C на Windows. Пример:

SHELL ["/bin/bash", "-c"]
  1. HEALTHCHECK
HEALTHCHECK [OPTIONS] CMD command

Определяет команду для проверки работоспособности контейнера. Параметры:

  • --interval: Интервал между проверками (по умолчанию 30 секунд).
  • --timeout: Таймаут для проверки (по умолчанию 30 секунд).
  • --retries: Количество попыток перед объявлением контейнера неработоспособным (по умолчанию 3). Пример:
HEALTHCHECK --interval=30s --timeout=10s \
CMD curl -f http://localhost/ || exit 1

В Dockerfile можно использовать и комментарии для документации, через #.

Лучшие практики и разбор

Лучшие практики:

  • Используйте минимальные базовые образы (например, alpine), чтобы уменьшить размер конечного образа.
  • Объединяйте команды (&&), чтобы минимизировать количество слоёв:
RUN apt-get update && apt-get install -y package1 package2
  • Используйте .dockerignore, чтобы исключить ненужные файлы из сборки.
  • Используйте COPY вместо ADD, если не требуется распаковка архивов.
  • Обеспечьте безопасность, используя минимальные привилегии (USER).

Давайте создадим пример хорошего Dockerfile по этим практикам. Представим, что у нас есть простое Python-приложение, которое запускает веб-сервер на Flask. Структура проекта предполагает, что в корне лежат файлы:

  • app.py — основной файл приложения.
  • requirements.txt — список зависимостей Python.
  • .dockerignore — файл для исключения ненужных файлов.

Какое содержимое в .dockerignore?

# Исключаем ненужные файлы
.git
__pycache__
*.log
*.pyc
.env

Теперь создадим Dockerfile, который будет следовать всем этим рекомендованным практикам:

# 1. Используем минимальный базовый образ
FROM python:3.9-alpine

# 2. Устанавливаем метаданные
LABEL maintainer="tim@mail.ru"

# 3. Устанавливаем переменные среды
ENV APP_HOME=/app
ENV PYTHONUNBUFFERED=1 # Для немедленного вывода логов

# 4. Создаем рабочую директорию
WORKDIR $APP_HOME

# 5. Копируем зависимости и устанавливаем их
COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del gcc musl-dev # Удаляем ненужные пакеты после установки

# 6. Копируем остальные файлы приложения
COPY . .

# 7. Создаем пользователя для работы приложения
RUN adduser -D appuser
USER appuser

# 8. Открываем порт
EXPOSE 5000

# 9. Запускаем приложение
CMD ["python", "app.py"]

Разбираем этот файл.

  1. Минимальный базовый образ:
FROM python:3.9-alpine

Мы использовали alpine версию Python, которая значительно меньше по размеру, чем стандартный образ. 2. Метаданные:

LABEL maintainer="tim@mail.ru"

Мы добавили метку для документации, чтобы было понятно, кто поддерживает образ. 3. Переменные среды:

ENV APP_HOME=/app
ENV PYTHONUNBUFFERED=1

APP_HOME задает рабочую директорию.

PYTHONUNBUFFERED=1 гарантирует, что логи будут выводиться немедленно, без буферизации.

  1. Рабочая директория:
WORKDIR $APP_HOME

Мы установили рабочую директорию, чтобы все последующие команды выполнялись в ней. 5. Установка зависимостей:

COPY requirements.txt .
RUN apk add --no-cache gcc musl-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del gcc musl-dev

Копируем только requirements.txt, чтобы кэширование работало эффективно. Устанавливаем временные зависимости (gcc, musl-dev) для сборки Python-пакетов, а затем удаляем их, чтобы уменьшить размер образа.

Используем флаг --no-cache-dir для pip, чтобы избежать сохранения кэша.

  1. Копирование файлов:
COPY . .

Копируем остальные файлы приложения в рабочую директорию. 7. Безопасность:

RUN adduser -D appuser
USER appuser

Создаём нового пользователя appuser, и используем его для запуска приложения. Это повышает безопасность, так как приложение не работает от имени root. 8. Открытие порта:

EXPOSE 5000

Информируем Docker о том, что приложение будет слушать порт 5000. 9. Команда для запуска:

CMD ["python", "app.py"]

Определяем команду для запуска приложения.

После того, как мы собрали контейнер, нам остаётся перейти в нужную директорию нашего проекта, выполнить docker build и docker run.